package com.mingshashan.building.easy.core.exception;

import com.mingshashan.building.easy.core.common.EasyConstants;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@ControllerAdvice
@Order(Integer.MAX_VALUE)
public class EasyControllerExceptionHandler {
    private Logger logger;

    public EasyControllerExceptionHandler() {
        this.logger = LoggerFactory.getLogger(this.getClass());
    }

    @SuppressWarnings("unchecked")
    @ExceptionHandler({EasyException.class, EasyRuntimeException.class})
    protected ResponseEntity<EasyErrorObject> handleAppException(final Throwable ex, final WebRequest request)
            throws Throwable {
        this.logger.error(ex.toString(), ex);
        EasyErrorObject errorObject = null;
        if (ex instanceof EasyException) {
            errorObject = ((EasyException) ex).getErrorObject();
        } else if (ex instanceof EasyRuntimeException) {
            errorObject = ((EasyRuntimeException) ex).getErrorObject();
        }
        if (errorObject == null) {
            this.logger.error("Get EasyErrorObject(from EasyException or EasyRuntimeException) is null");
            final int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
            final String code = ex.getClass().getSimpleName();
            final String error = ex.getClass().getName();
            final String message = ex.getMessage();
            errorObject = new EasyErrorObject(status, EasyConstants.RESPONSE_ERROR_CODE, error, message);
        }
        return responseEntityResolver(request, errorObject);
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    protected ResponseEntity<EasyErrorObject> handleMethodArgumentNotValid(final MethodArgumentNotValidException ex,
                                                                           final WebRequest request) {
        this.logger.error(ex.toString(), (Throwable) ex);
        final List<String> errors = new ArrayList<String>();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            if (error instanceof FieldError) {
                FieldError fieldError = (FieldError) error;
                errors.add("'" + fieldError.getObjectName() + "." + fieldError.getField() + "' "
                        + fieldError.getDefaultMessage());
            } else {
                errors.add(
                        "'" + ((ObjectError) error).getObjectName() + "' " + ((ObjectError) error).getDefaultMessage());
            }
            return;
        });
        final int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        final String value = ex.getClass().getSimpleName();
        final String error = ex.getClass().getName();
        final String message = errors.stream().collect(Collectors.joining(","));
        final EasyErrorObject errorObject = new EasyErrorObject(status, EasyConstants.RESPONSE_ERROR_CODE, error,
                message);
        return responseEntityResolver(request, errorObject);
    }

    @ExceptionHandler({BindException.class})
    public ResponseEntity<EasyErrorObject> handleBindException(final BindException ex, final WebRequest request) {
        this.logger.error(ex.toString(), (Throwable) ex);
        final List<String> errors = new ArrayList<String>();
        ex.getBindingResult().getAllErrors().forEach(error -> {
            if (error instanceof FieldError) {
                FieldError fieldError = (FieldError) error;
                errors.add("'" + fieldError.getObjectName() + "." + fieldError.getField() + "' "
                        + fieldError.getDefaultMessage());
            } else {
                errors.add(
                        "'" + ((ObjectError) error).getObjectName() + "' " + ((ObjectError) error).getDefaultMessage());
            }
            return;
        });
        final int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        final String value = ex.getClass().getSimpleName();
        final String error = ex.getClass().getName();
        final String message = errors.stream().collect(Collectors.joining(","));
        final EasyErrorObject errorObject = new EasyErrorObject(status, EasyConstants.RESPONSE_ERROR_CODE, error,
                message);
        return responseEntityResolver(request, errorObject);
    }


    @ExceptionHandler({RestClientResponseException.class})
    protected ResponseEntity<EasyErrorObject> handleRestClientResponseException(final RestClientResponseException ex,
                                                                                final WebRequest request) {
        this.logger.error(ex.toString(), (Throwable) ex);
        final int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        final String code = ex.getClass().getSimpleName();
        final String error = ex.getClass().getName();
        final String message = "(RestClientResponse with status code: " + ex.getRawStatusCode() + ")" + ex.getMessage()
                + ":" + ex.getResponseBodyAsString();
        final EasyErrorObject errorObject = new EasyErrorObject(status, code, error, message);
        return responseEntityResolver(request, errorObject);
    }

    @ExceptionHandler({Throwable.class})
    protected ResponseEntity<EasyErrorObject> handleThrowable(final Throwable ex, final WebRequest request) {
        this.logger.error(ex.toString(), ex);
        final int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        final String code = ex.getClass().getSimpleName();
        final String error = ex.getClass().getName();
        final String message = ex.getMessage();
        final EasyErrorObject errorObject = new EasyErrorObject(status, code, error, message);
        return responseEntityResolver(request, errorObject);
    }

    private ResponseEntity<EasyErrorObject> responseEntityResolver(WebRequest request, EasyErrorObject errorObject) {
        String date = DateFormatUtils.format(new Date(), EasyConstants.DATE_TIME2_FORMAT);
        errorObject.setDate(date);
        errorObject.setPath(request.getContextPath());
        HttpServletRequest httpServletRequest = (HttpServletRequest) ((ServletWebRequest)request).getNativeRequest();
        errorObject.setPath(httpServletRequest.getRequestURI());
        errorObject.setMethod(httpServletRequest.getMethod());
        return (ResponseEntity<EasyErrorObject>) new ResponseEntity(errorObject,
                HttpStatus.resolve(errorObject.getStatus()));
    }
}